﻿using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace OntologyCache
{
    [Serializable()]
    internal  class OBO_LKP_CONST
    {
         public const string OntName  = "OntologyName";
         public const string Name     = "name";
         public const string ID       = "id";
         public const string Defn     = "def";
         public const string Obsolete = "is_obsolete";
         public const string ISA      = "is_a";
    }

    [Serializable()]
    public class OntologyNode
    {
        public string OntologyName   = null;
        public string id             = null;
        public string Name           = null;
        public string Alias          = null; // used for custom tags
        public string Definition     = null;
        public object tag            = null;
        public string url            = null;
        public bool IsTopLevelNode   = true;
        public bool isObsolete = false;
        public string isaName = null;
        public List<OntologyNode> Children = new List<OntologyNode>();


        public  void  DumpNode()
        {
 	        Console.WriteLine( "ID : " +  id );
            Console.WriteLine( "Description : " +  Definition );
            Console.WriteLine( "Name : " +  Name);
            Console.WriteLine("Alias : " + Alias);
            if( isaName == null )
            {
                Console.WriteLine( "isaName : " +  isaName );
            }
            if (OntologyName == null)
            {
                Console.WriteLine("ONTOLOGY IS NULL");
            }
            
        }
    }


    // These are all the tags for a given ontology
    [Serializable()]
    public class OntologyInfo
    {
        public Dictionary<string, OntologyNode> m_TermLookup = new Dictionary<string, OntologyNode>(StringComparer.OrdinalIgnoreCase);
        public Dictionary<string, OntologyNode> m_OntologyTree = new Dictionary<string, OntologyNode>();
        public string OntologyName;
        public void Clear()
        {
            m_TermLookup.Clear();
        }
        
        public void AddTerm(string TermName, OntologyNode TermValues)
        {
            try
            {
                m_TermLookup.Add(TermName,TermValues);
                FixupTreeStructure(TermName,TermValues );
            }
            catch(Exception Ex)
            {

            }
        }
        
        private OntologyNode FindOrAdd(string ID)
        {
            OntologyNode Node = null;
            try
            {
                
                if (m_OntologyTree.ContainsKey(ID))
                {
                    Node = m_OntologyTree[ID];
                }
                else
                {
                    Node = new OntologyNode();
                    m_OntologyTree.Add(ID, Node);
                }
            }
            catch (Exception Ex)
            {

            }
            return Node;
        }
        
        public void DumpTags()
        {
            Console.WriteLine("**************************STARTING DUMP****************************************");

            foreach (var Value in m_TermLookup)
            {
                
                Console.WriteLine("Dumping information for Term----" + Value.Key);
                OntologyNode on = Value.Value;
                on.DumpNode();
                
                Console.WriteLine("============================================================================");
                Console.ReadLine();

            }

            Console.WriteLine("**************************END DUMP****************************************");

        }
        
        private void FixupTreeStructure( string TermName, OntologyNode TermValues )
        {
            // if the term has a ISA attribute set the top level node to false
            
            try
            {
                OntologyNode child = null;
                OntologyNode parent = null;   
                child = FindOrAdd(TermValues.Name);
                FillChild(child, TermValues);                
                if( TermValues.isaName != null )
                {
                    child.IsTopLevelNode = false;
                    parent = FindOrAdd(TermValues.isaName);
//                    parent.id = TermValues.id;
                    parent.Children.Add(child);
                }
            }
            catch (Exception ex)
            {

            }

        }

        private void FillChild(OntologyNode Child, OntologyNode TermValues)
        {
            Child.id = TermValues.id;
            Child.Name = TermValues.Name;
            Child.Definition = TermValues.Definition;
            Child.OntologyName = TermValues.OntologyName;
        }        
    }


    public class OBOParser
    {
        private MatchCollection GetTermMatches(string FileName  )
        {
            
            if (false == File.Exists(FileName))
            {
                return null;
            }
            string OBOText = File.ReadAllText(FileName);
            string regPattern = "\\[Term\\](.*?)\\n\\n";


            Regex rg = new Regex(regPattern, RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace);
            MatchCollection matches = rg.Matches(OBOText);
            return matches;

        }
         private string Sanitize(string V1, string V2)
        {
            string S = V2;

            if (V1 == OBO_LKP_CONST.Name)
            {
                return SanitizeName( V2);
            }

            if (V1 == OBO_LKP_CONST.ID)
            {
                return SanitizeID(V2);
            }
            return S;
        }


        private string SanitizeName(string NameVal)
        {
            return NameVal.Replace("_", " ");
        }

        private string SanitizeID( string IDVal )
        {
            if( IDVal.IndexOfAny(new char[]{'!','_'}) >= 0 )
            {
                return null;
            }
            
            if( true == IDVal.StartsWith(@"http://",StringComparison.InvariantCultureIgnoreCase ))
            {
                return null;
            }

            if( true == IDVal.Contains("synonym" ) )
            {
                return null;
            }
            return IDVal;
        }

        private void TermSanitize(OntologyNode on )
        {
            if( on.Definition == null )
            {
                on.Definition = on.isaName ?? ""; 
            }
        }

        public void Parse(string FileName, OntologyInfo tagLookup, string OntologyName)
        {

            MatchCollection matches = GetTermMatches(FileName);
            if (matches == null)
            {
                return;
            }
            foreach (Match m in matches)
            {

                string Term = m.Groups[1].Value;
                StringReader sr = new StringReader(Term);
                OntologyNode on = new OntologyNode();
                
                while (true)
                {
                    string NVPair = sr.ReadLine();
                    if (NVPair == null )
                    {
                        // We are done parsing the entire term. Any whole term validation/sanitization needs to happen here 
                        TermSanitize(on);
                        on.OntologyName = OntologyName;
                        break;
                    }
                    int iIndex = NVPair.IndexOf(":");
                    if (iIndex != -1)
                    {
                        string V1 = NVPair.Substring(0,iIndex).Trim();
                        iIndex++;
                        string V2 = NVPair.Substring(iIndex, NVPair.Length - iIndex).Trim();
                        V1 = V1.Trim();
                        V2 = V2.Trim();
                        string V2_Sanitize = Sanitize(V1, V2);
                        if (V2_Sanitize != null)
                        {
                            try
                            {
                                FillOntologyNode(on,V1,V2);
                            }
                            catch (Exception ex)
                            {
                                // to avoid multiple instances like realtive_synonym etc
                            }
                        }
                    }
                }
                if( on.isObsolete == false )
                {
                    tagLookup.AddTerm(on.Name , on );
                }
            }

            
        }

        public void FillOntologyNode( OntologyNode node, string PropName, string PropVal )
        {
            switch( PropName )
            {
                case OBO_LKP_CONST.OntName :
                    node.OntologyName = PropVal;
                    break;
                case OBO_LKP_CONST.Name    :
                    node.Name = PropVal;
                    break;
                case OBO_LKP_CONST.ID      :
                    node.id = PropVal;
                    break;
                case OBO_LKP_CONST.Defn    :
                    node.Definition = PropVal;
                    break;
                case OBO_LKP_CONST.ISA     :
                    string S1 = PropVal.Split(new char[] { '!' })[1]; // the isa tag is of the form ID ! Name
                    node.isaName = S1.Trim();                     
                    break;
                case OBO_LKP_CONST.Obsolete:
                    node.isObsolete = true;
                    break;

            }
        }

        

    }


}



